/*

MiscSoundView
Version 1.2
Copyright (c) 1995 by Sean Luke
Donated to the MiscKit

Permission to use, copy, modify, and distribute this material 
for any purpose and without fee, under the restrictions as noted 
in the MiscKit copyright notice, is hereby granted, provided that
the MiscKit copyright notice and this permission notice 
appear in all source copies, and that the author's name shall not
be used in advertising or publicity pertaining to this 
material without the specific, prior written permission 
of the author.  SEAN O. LUKE  MAKES NO REPRESENTATIONS ABOUT THE
ACCURACY OR SUITABILITY OF THIS MATERIAL FOR ANY PURPOSE.  
IT IS PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES.

*/

// Known Bugs:

//	1)  Play Mark doesn't disappear unless you tell it to.
//	2)	Rulers don't appear until explicitly told to.
//	3)	Ruler values slowly drift (rounding error)

#import "MiscSoundView.h"
#import <math.h>
#import <stdio.h>

// CONVENIENCE FUNCTIONS

double scroll_to_reduction (double scrollValue, double ratio)
	// scrollValue must be a number between 1 and 0
	// ratio is sample count / display units
	// returns -1 if error
	// this function considers roundoff to integers,
	// and a maximum value at integer point
	{
	if (scrollValue>1.0||scrollValue<0.0) 
		{
		printf ("scroll_to_reduction ERROR:  scrollValue is %f, ratio is%f\n", scrollValue, ratio);
		return -1.0;
		}
	return ceil(pow(ceil(ratio),scrollValue));
	}

	
double reduction_to_scroll (double reduction, double ratio)	
	// reduction must be a number above 1, preferably < ratio
	// ratio is sample count / display units
	// returns -1 if error
	// this function does not consider roundoff to integers...
	// but does consider a maximum value at integer point
	{
	if (reduction<1.0) 
		{
		printf ("reduction_to_scroll ERROR:  reduction is %f, ratio is %f\n", reduction, ratio);
		return -1.0;
		}
	return log(reduction)/log(ceil(ratio));
	}
	
	
// private function that rounds x to the nearest integer.

int MISCSOUNDVIEW_round(float x)
	{
	if (fabs(ceil(x)-x)<=0.5) return (int) ceil(x);
	return (int) floor(x);
	}


@implementation MiscSoundView

// Private method that tells the SoundView to wipe itself clean when running

- _wipeClean
	{
	wipe_clean=YES;
	return self;
	}


// Private method that modify the bounds/frame rect...

- _adjustBounds
{
	// This assumes that MiscSoundView is in a scrollView!
	// It also does _not_ draw the MiscSoundView...
	
	id scroll_view;
	NXSize ThisSize;
	
	if ((scroll_view=[[self superview] superview])==NULL) 
		{
		return NULL;		// not in scrollView!
		}
	
	[scroll_view getContentSize: &ThisSize];	
	[super sizeTo: bounds.size.width : 
		ThisSize.height- (display_x_axis_marks ? MISCSOUNDVIEW_RULER_HEIGHT +
			MISCSOUNDVIEW_PLAY_MARK_HEIGHT : 0)];	
	return self;
}

- adjustBounds:sender
{	
	if ([self _adjustBounds]==NULL) {return NULL;}
	[self _wipeClean];
	[self display];
	return self;
}

- sizeTo:(NXCoord)width :(NXCoord)height
	{
	id returnval=[super sizeTo:width:height];
	[self _adjustBounds];
	[self _wipeClean];
	return returnval;
	}


- initFrame:(const NXRect*) frameRect
{
	id returnval=[super initFrame:frameRect];
	display_x_axis_marks=NO;
	display_y_axis_grid=NO;
	display_zero_line=NO;
	display_labels=YES;
	y_display_format=MISCSOUNDVIEW_YAXIS_DISPLAY_FORMAT_SIXTEEN;
	minor_tick_spacing=1000.0;
	minor_tick_spacing_format=MISCSOUNDVIEW_XAXIS_SPACING_FORMAT_SAMPLES;
	major_tick_spacing=10;
	old_play_mark=-1;
	play_mark=-1;
	scroll_to_reflect_playing=NO;
	only_change_play_mark=NO;
	[self _wipeClean];
	[self _adjustBounds];
	return returnval;
}


- drawSelf:(const NXRect *)rects :(int)rectCount
{


//-------------------------------------
//	Some DrawSelf Free-Form Poetry:
//
//	drawSelf is probably
//	the most evil monolithic Obj-C ever
//	created.  I can't bother
//	prettifying it, though I might
//	make it
//	barely understandable.
//-------------------------------------



	NXSize temp;
	BOOL display_minor_ticks;
	float maxval,lastval;
	int a;
	int numberOfSamples;
	float tick_height;
	int startsample,endsample,xx;
	id returnval=self;
	int true_sample_count=[sound sampleCount];
		
	// Draw SoundView stuff
	if (!only_change_play_mark)
		{
		[self setClipping:YES];
		returnval=[super drawSelf: rects: rectCount];
		}
	[self setClipping:NO];

	/*     
	 *
	 *
	 * Drawing the Y-Axis elements (Amplitude and Zero Line)...
	 *
	 *
	 *
	 */
	
	if (display_y_axis_grid&&!only_change_play_mark&&true_sample_count)
		{
		if (y_display_format==MISCSOUNDVIEW_YAXIS_DISPLAY_FORMAT_DECIBEL)
			{
			maxval=SNDConvertDecibelsToLinear(100.0);
			lastval=bounds.size.height/2;
			
			for (a=19;a>=10;a--)
				{
				temp.height=lastval-SNDConvertDecibelsToLinear
					((float)a*5)/maxval*(bounds.size.height/2);
				temp.width=0;
				[self convertSize:&temp toView:nil];
				if (temp.height>2.0)
					{
					PSsetlinewidth(0);
					PSsetgray(NX_DKGRAY);
					PSmoveto (rects[0].origin.x,SNDConvertDecibelsToLinear
						((float)a*5)/maxval*(bounds.size.height/2));
					PSrlineto(rects[0].size.width,0);
					PSsetlinewidth(0);
					PSmoveto (rects[0].origin.x,0-SNDConvertDecibelsToLinear
						((float)a*5)/maxval*(bounds.size.height/2));
					PSrlineto(rects[0].size.width,0);
					PSstroke();
					}
				lastval=SNDConvertDecibelsToLinear
					((float)a*5)/maxval*(bounds.size.height/2);
				}
			}
		else if (y_display_format==MISCSOUNDVIEW_YAXIS_DISPLAY_FORMAT_SIXTEEN)
			{
			for (a=1;a<8;a++)
				{
				PSsetlinewidth(0);
				PSsetgray(NX_DKGRAY);
				PSmoveto (rects[0].origin.x,(bounds.size.height/2)/8*a);
				PSrlineto(rects[0].size.width,0);
				PSsetlinewidth(0);
				PSmoveto (rects[0].origin.x,0-(bounds.size.height/2)/8*a);
				PSrlineto(rects[0].size.width,0);
				PSstroke();	
				}		
			}
		else if (y_display_format==MISCSOUNDVIEW_YAXIS_DISPLAY_FORMAT_TWENTY)
			{
			for (a=1;a<10;a++)
				{
				PSsetlinewidth(0);
				PSsetgray(NX_DKGRAY);
				PSmoveto (rects[0].origin.x,(bounds.size.height/2)/10*a);
				PSrlineto(rects[0].size.width,0);
				PSsetlinewidth(0);
				PSmoveto (rects[0].origin.x,0-(bounds.size.height/2)/10*a);
				PSrlineto(rects[0].size.width,0);
				PSstroke();	
				}		
			}
		else // y_display_format==MISCSOUNDVIEW_YAXIS_DISPLAY_FORMAT_TWENTYFOUR
			{
			for (a=1;a<12;a++)
				{
				PSsetlinewidth(0);
				PSsetgray(NX_DKGRAY);
				PSmoveto (rects[0].origin.x,(bounds.size.height/2)/12*a);
				PSrlineto(rects[0].size.width,0);
				PSsetlinewidth(0);
				PSmoveto (rects[0].origin.x,0-(bounds.size.height/2)/12*a);
				PSrlineto(rects[0].size.width,0);
				PSstroke();	
				}		
			}
		}
	if (display_zero_line&&!only_change_play_mark&&true_sample_count)
		{
		PSsetlinewidth(0);
		PSsetgray(NX_DKGRAY);
		PSmoveto (rects[0].origin.x,0);
		PSrlineto(rects[0].size.width,0);
		PSstroke();
		}



	/*     
	 *
	 *
	 * Drawing the X-Axis elements (Ruler and Play-Mark)...
	 *
	 *
	 *
	 */
		
		
	if (display_x_axis_marks&&(minor_tick_spacing!=0.0)&&major_tick_spacing)				
	
	
		// display the axis
		
		{
		double scale_factor;
		NXRect temprect;
		char tempstring[20];
		float print_value;
		BOOL display_major_ticks;
		BOOL enough_room_for_labels;
		
		// to begin, figure out the scaling factor:
		
		scale_factor=bounds.size.height/frame.size.height;
		
		// then draw ticks
		
		tick_height=MISCSOUNDVIEW_RULER_HEIGHT/2;

		if  (minor_tick_spacing_format==
						MISCSOUNDVIEW_XAXIS_SPACING_FORMAT_SAMPLES)
								// display in samples
			{
			numberOfSamples=(int)minor_tick_spacing;
			}
		else if (minor_tick_spacing_format==
						MISCSOUNDVIEW_XAXIS_SPACING_FORMAT_PERCENT)
								// display in percentage
			{
			numberOfSamples=(int)
						(minor_tick_spacing/100.0*[sound sampleCount]);
			}
		else					// display in seconds
			{
			numberOfSamples=(int)(minor_tick_spacing*[sound samplingRate]);
			}
		
		if (!numberOfSamples) numberOfSamples=1;		// for divide-by-zero	
		
		startsample=(((int) (rects[0].origin.x*reductionFactor))
							/numberOfSamples)*numberOfSamples;
			
				// note the integer division above!!!!  This
				// does automatic rounding properly...
		
		display_minor_ticks=(BOOL)(((float)numberOfSamples)/reductionFactor>=
			MISCSOUNDVIEW_TICK_MINIMUM_SPACING);
		display_major_ticks=(BOOL)(((float)numberOfSamples)
			*major_tick_spacing/reductionFactor>=
			MISCSOUNDVIEW_TICK_MINIMUM_SPACING);
		enough_room_for_labels=(BOOL)(((float)numberOfSamples)
			*major_tick_spacing/reductionFactor>=
			MISCSOUNDVIEW_TICK_MINIMUM_SPACING_WITH_LABELS);

		endsample=((int)(rects[0].size.width+rects[0].origin.x))
			*reductionFactor;
		
		
		
		// Erase ruler if needed...
		
		if (!only_change_play_mark&&true_sample_count)
		{
			// wipe the ruler clean if needed
	
			if (wipe_clean)
				{
				PSsetgray(NX_WHITE);
				temprect.origin.x=rects[0].origin.x;
				temprect.origin.y=bounds.size.height/2;
				temprect.size.width=rects[0].size.width;
				temprect.size.height=
					(MISCSOUNDVIEW_RULER_HEIGHT+MISCSOUNDVIEW_PLAY_MARK_HEIGHT)
					*scale_factor;
				PSrectfill(temprect.origin.x,temprect.origin.y,
					temprect.size.width,temprect.size.height);
				wipe_clean=NO;		// finished
				}
	
			// Draw ruler lines
		
			PSsetlinewidth(0);
			PSsetgray(NX_BLACK);
			PSmoveto(rects[0].origin.x, bounds.size.height/2);
			PSrlineto(rects[0].size.width, 0);
			PSmoveto(rects[0].origin.x,bounds.size.height/2+
				MISCSOUNDVIEW_PLAY_MARK_HEIGHT*scale_factor);
			PSrlineto(rects[0].size.width,0);
		}
		PSstroke();
		
		
		// Next, erase old play mark (if any) and draw new play-mark
		
		if (old_play_mark!=-1)
			{
			PSsetgray(NX_WHITE);
			PSmoveto(old_play_mark/reductionFactor+bounds.origin.x,
				bounds.size.height/2+1*scale_factor);
			PSrlineto(0,2*scale_factor);
			PSrmoveto(-1,-1*scale_factor);
			PSrlineto(2,0);
			}
		PSstroke();
		old_play_mark=play_mark;
		
		if (old_play_mark!=-1&&true_sample_count)	// don't draw if no samples
			{
			PSsetgray(NX_BLACK);
			PSmoveto(old_play_mark/reductionFactor+bounds.origin.x,
				bounds.size.height/2+1*scale_factor);
			PSrlineto(0,2*scale_factor);
			PSrmoveto(-1,-1*scale_factor);
			PSrlineto(2,0);
			}
		PSstroke();



		// finally, draw the ticks and add the labels

		if (!only_change_play_mark&&true_sample_count)
		{
			// here we draw the label equal to or directly to the left of
			// startsample, to fix a little bug in drawing that comes up 
			// when scrolling.
			
			xx=(startsample/(numberOfSamples*major_tick_spacing))		
					*(numberOfSamples*major_tick_spacing);
								// note integer division!!!
			
			PSmoveto(((float)xx)/reductionFactor+bounds.origin.x,
				bounds.size.height/2+(1+MISCSOUNDVIEW_PLAY_MARK_HEIGHT
				*scale_factor));
						// should the 1 be modified by the scale factor?
			
			PSrmoveto(0,tick_height*scale_factor);
			if (display_labels&&enough_room_for_labels)
				{
				if (minor_tick_spacing_format==
					MISCSOUNDVIEW_XAXIS_SPACING_FORMAT_SAMPLES)
					print_value=(float)xx;
				else if (minor_tick_spacing_format==
					MISCSOUNDVIEW_XAXIS_SPACING_FORMAT_PERCENT)
					print_value=((float)xx)/((float)[sound sampleCount])*100;
				else print_value=((float)xx)/(float)[sound samplingRate];
				PSgsave();
				PSinitmatrix();
				PSrmoveto(0,1);
				if (minor_tick_spacing_format==
					MISCSOUNDVIEW_XAXIS_SPACING_FORMAT_SAMPLES)
					sprintf(tempstring, " %d", (int)print_value);
							// use int value
							
				else if (minor_tick_spacing_format==
					MISCSOUNDVIEW_XAXIS_SPACING_FORMAT_PERCENT)
					sprintf(tempstring, " %d",
					MISCSOUNDVIEW_round(print_value));
				else sprintf(tempstring, " %.3f", print_value);			
							// use float value
							
				PSselectfont("Helvetica",8);
				PSshow(tempstring);
				PSgrestore();
				}
				
			// end bug fix.  We continue, now drawing the ticks 
			// and adding labels...

		
			if (minor_tick_spacing!=0.0) 
			for (xx=startsample;xx<=endsample;xx+=numberOfSamples)
				{
				// Prepare the ruler for labelling
				
				if (display_major_ticks&&major_tick_spacing&&
					(!(xx%(numberOfSamples*major_tick_spacing))))
					{
					PSsetgray(NX_BLACK);
					PSmoveto(((float)xx)/reductionFactor+bounds.origin.x,
							bounds.size.height/2+
							(1+MISCSOUNDVIEW_PLAY_MARK_HEIGHT*scale_factor));
					PSrlineto(0,tick_height*scale_factor);
					PSgsave();
					PSstroke();
					PSgrestore();	// preserves path and (more importantly)
									// currentpoint
					
					// And Add Label
					
					if (display_labels&&enough_room_for_labels)
						{
						if (minor_tick_spacing_format==
							MISCSOUNDVIEW_XAXIS_SPACING_FORMAT_SAMPLES)
												// display in samples
												
							print_value=(float)xx;
						else if (minor_tick_spacing_format==
							MISCSOUNDVIEW_XAXIS_SPACING_FORMAT_PERCENT)
												// display in percentage
												
							print_value=((float)xx)/
								((float)[sound sampleCount])*100;
								
						else					// display in seconds
							print_value=((float)xx)/(float)
								[sound samplingRate];	
						PSgsave();
						PSinitmatrix();
						PSrmoveto(0,1);
						if (minor_tick_spacing_format==
							MISCSOUNDVIEW_XAXIS_SPACING_FORMAT_SAMPLES)
							sprintf(tempstring, " %d", (int)print_value);		
									// use int value
									
						else if (minor_tick_spacing_format==
							MISCSOUNDVIEW_XAXIS_SPACING_FORMAT_PERCENT)
							sprintf(tempstring, " %d", 
								MISCSOUNDVIEW_round(print_value));
									// use rounded int value
									
						else sprintf(tempstring, " %.3f", print_value);	
									// use float value
									
						PSselectfont("Helvetica",8);
						PSshow(tempstring);
						PSgrestore();
						}
					}
				else
					{
					if (display_minor_ticks)
						{
						PSsetgray(NX_LTGRAY);
						PSmoveto(((float)xx)/reductionFactor+
							bounds.origin.x,bounds.size.height/2+
							(1+MISCSOUNDVIEW_PLAY_MARK_HEIGHT*scale_factor));
						PSrlineto(0,tick_height/2*scale_factor);					
								// minor ticks are 1/2 height of major ticks
								
						PSstroke();
						}
					}
				PSnewpath(); // deletes path saved when drawing major ticks.	
				}
			}		// play-mark bracket (see above)
		}

	// Return to normal...
	[self setClipping:YES];	
	return returnval;
}



- getSelection:(int *)firstSample size:(int *)sampleCount
// a fixed version of getSelection which returns the proper selected area
// POSSIBLE BUG ALERT:  NeXT's stuff may depend on the broken getSelection!

{
	int samples=[[self sound] sampleCount];
	[super getSelection:firstSample size:sampleCount];
	// Now we check and modify accordingly:
	if (*firstSample+*sampleCount>samples) *sampleCount=samples-*firstSample;
	return self;
}



- setSelection:(int)firstSample size:(int)sampleCount
// a fixed version of setSelection which sets the proper selected area
// POSSIBLE BUG ALERT:  NeXT's stuff may depend on the broken setSelection!

{
	int samples=[[self sound] sampleCount];
	if (firstSample+sampleCount>=samples)
		// we make certain that the right amount is selected!
		// Note that you can't just set the selection beyond the sound
		// value, because the soundView doesn't allow it.
		// so here we physically set the soundView's selection rect...
		// hope this doesn't cause weird bugs...
		{
		[super setSelection:firstSample size:sampleCount];	
			// as good as can be done...
		selectionRect.size.width*=1.1;	
			// this makes the selection a tiny bit larger
		}
	else [super setSelection:firstSample size:sampleCount];
	return self;
}



- selectAll:sender
// a fixed version of selectAll which sets the proper selected area
// POSSIBLE BUG ALERT:  NeXT's stuff may depend on the broken selectAll!

{
	[self setSelection:0 size:[[self sound] sampleCount]];
	return self;
}


- set:
	(BOOL) displayXAxis:
	(BOOL) displayYAxis:
	(BOOL) displayLabels:
	(BOOL) displayZeroLine:
	(int) majorTickSpacing:
	(float) minorTickSpacing:
	(int) minorTickSpacingFormat:
	(int) yDisplayFormat:
	(BOOL) scrollToReflectPlaying
	
	// Sets these values...
	
	
	{
	display_x_axis_marks=displayXAxis;
	display_y_axis_grid=displayYAxis;
	display_labels=displayLabels;
	display_zero_line=displayZeroLine;
	scroll_to_reflect_playing=scrollToReflectPlaying;
	
	// first set the default spacing in case this spacing is invalid
	major_tick_spacing=5;
	if (minorTickSpacingFormat==
		MISCSOUNDVIEW_XAXIS_SPACING_FORMAT_PERCENT) minor_tick_spacing=10.0;
	else if (minorTickSpacingFormat==
		MISCSOUNDVIEW_XAXIS_SPACING_FORMAT_SAMPLES) minor_tick_spacing=1000;
	else if (minorTickSpacingFormat==
		MISCSOUNDVIEW_XAXIS_SPACING_FORMAT_SECONDS) minor_tick_spacing=1;
	
	// then change to the user's spacing if his spacing is acceptable
	if (majorTickSpacing>0)
		major_tick_spacing=majorTickSpacing;
	if ((minorTickSpacing>0.0&&
		(minorTickSpacingFormat!=
		MISCSOUNDVIEW_XAXIS_SPACING_FORMAT_SAMPLES)) ||
	    (minorTickSpacing>=1.0&&
		(minorTickSpacingFormat==
		MISCSOUNDVIEW_XAXIS_SPACING_FORMAT_SAMPLES)))
		minor_tick_spacing=minorTickSpacing;
	
	minor_tick_spacing_format=minorTickSpacingFormat;
	y_display_format=yDisplayFormat;
	[self _adjustBounds];
	
	return self;
	}
	
- (BOOL) xAxisDisplayed
	{
	return display_x_axis_marks;
	}

- (BOOL) yAxisDisplayed
	{
	return display_y_axis_grid;
	}

- (BOOL) zeroLineDisplayed
	{
	return display_zero_line;
	}

- (BOOL) labelsDisplayed
	{
	return display_labels;
	}

- (BOOL) scrollToReflectPlaying
	{
	return scroll_to_reflect_playing;
	}

- (int) majorTickSpacing
	{
	return major_tick_spacing;
	}

- (float) minorTickSpacing
	{
	return minor_tick_spacing;
	}

- (int) minorTickSpacingFormat
	{
	return minor_tick_spacing_format;
	}

- (int) yDisplayFormat
	{
	return y_display_format;
	}


- scrollToSelection:sender
	// scrolls if there's room to do so
	{
	int first_sample,number_of_samples;
	int total_samples;
	NXRect bounds_rect;
	NXRect visible_rect;
	NXPoint scroll_point;
	id currentScrollView=[[self superview] superview];
	
	if (currentScrollView==NULL) return NULL;		// not in scrollView!
	
	total_samples=[[self sound] sampleCount];
	[self getSelection:&first_sample size:&number_of_samples];
	[self getBounds:&bounds_rect];		// get new bounds information if any
	[self getVisibleRect:&visible_rect];
	
	if (!total_samples) total_samples=1;		// kills divide-by-zero
	
		scroll_point.x= ((bounds_rect.size.width*
		(double)first_sample)/(double) total_samples);
	scroll_point.y= (bounds_rect.origin.y);
	if (scroll_point.x+visible_rect.size.width>
		bounds.size.width) 
			scroll_point.x=bounds.size.width-visible_rect.size.width;
			
	[self scrollPoint: &scroll_point];
	[currentScrollView reflectScroll: [self superview]];
	return self;
	}
	
- scrollToSample:(int) samp
	// scrolls to samp if there's room to do so.
	{
	int first_sample,number_of_samples;
	int total_samples;
	NXRect bounds_rect;
	NXRect visible_rect;
	NXPoint scroll_point;
	id currentScrollView=[[self superview] superview];
	
	if (currentScrollView==NULL) return NULL;		// not in scrollView!
	
	total_samples=[[self sound] sampleCount];
	first_sample=samp;
	number_of_samples=0;
	[self getBounds:&bounds_rect];		// get new bounds information if any
	[self getVisibleRect:&visible_rect];
	
	if (!total_samples) total_samples=1;		// kills divide-by-zero
	
	scroll_point.x= ((bounds_rect.size.width*
		(double)first_sample)/(double) total_samples);
	scroll_point.y= (bounds_rect.origin.y);
	if (scroll_point.x+visible_rect.size.width>
		bounds.size.width) 
			scroll_point.x=bounds.size.width-visible_rect.size.width;
			
	[self scrollPoint: &scroll_point];
	[currentScrollView reflectScroll: [self superview]];
	return self;
	}
	
- (int)scrollSample
	// returns the current scrolled sample (leftmost)
	{
	int firstViewSample;
	NXRect visibleRect;
	double ReductionFactor;
	
	ReductionFactor= (double) [self reductionFactor];
	[self getVisibleRect: &visibleRect];
	firstViewSample=(int)(visibleRect.origin.x*ReductionFactor);
	if (sound)
		if (firstViewSample>=[sound sampleCount]) 
			firstViewSample=[sound sampleCount]-1;
	if (firstViewSample<0) firstViewSample=0;
	return firstViewSample;
	}

- setPlayMark:(int)sample
	// -1 is default position
	{
	old_play_mark=play_mark;
	play_mark=(float)sample;
	if (display_x_axis_marks&&(minor_tick_spacing!=0.0)&&major_tick_spacing)
		// we're showing ticks...
	only_change_play_mark=YES;
	[self display];
	only_change_play_mark=NO;
	return self;
	}
	
	
	

- (double) _scrollValue							// a private method
	{
	NXSize content_size;
	id currentScrollView;
	currentScrollView=[[self superview] superview];
	
	if (currentScrollView==NULL) return 0;		// not in scrollView!
	
	[currentScrollView getContentSize:&content_size];
	
	return reduction_to_scroll
		( [self reductionFactor],
		  ((double)[sound sampleCount])
		  /(double)content_size.width);
	}
	


- _setScrollValue:(double) this_value		
					// private method. this_value must be between 1 and 0
	{
	NXSize content_size;
	double new_reduction_factor;
	int first_sample, number_of_samples;
	id currentScrollView=[[self superview] superview];
	
	if (currentScrollView==NULL) return NULL;		// not in scrollView!
	
	[currentScrollView getContentSize:&content_size];

	[self getSelection:&first_sample size:&number_of_samples];

	new_reduction_factor= scroll_to_reduction		
					// is this where the problem lies?
		( this_value,
		  ((double)[sound sampleCount])
		  /(double)content_size.width);
	
	if (new_reduction_factor!=-1.0)		// it's not an error
		{
		[self setReductionFactor:new_reduction_factor];
		[self setSelection:first_sample size: number_of_samples];
		return self;
		}
	else
		{
		[self setSelection:first_sample size: number_of_samples];
		return NULL;
		}
	}
	
	
	
- _zoomTo:(double) scroll_value					// a private method
	{
	id returnval;
	int first_sample, number_of_samples;
	[window disableFlushWindow];
	[self getSelection:&first_sample size:&number_of_samples];
		
	if (scroll_value>1.0) scroll_value=1.0;
	if (scroll_value<0.0) scroll_value=0.0;
	returnval=[self _setScrollValue:scroll_value];
	[self setSelection:first_sample size: number_of_samples];
	[self display];
	[window reenableFlushWindow];
	[window flushWindowIfNeeded];
	return returnval;
	}
	
- getZoomValueFrom:sender
	{
	return [self _zoomTo:[sender floatValue]];
	}
		
- takeIntValueFrom:sender
	{
	int temp_play_mark=[sender intValue];
	id snd=[self sound];
	int samples;
	
	if (snd==NULL) samples=0;
	else samples=[snd sampleCount];
	
	if (temp_play_mark>=0&&
		temp_play_mark<samples&&scroll_to_reflect_playing)
		{
		[self scrollToSample:temp_play_mark];
		[self setPlayMark:temp_play_mark];
		}
	else [self setPlayMark:-1];
	return self;
	}	
	
- zoomAllIn:this_soundview
	{
	[self _zoomTo:0.0];
	[self scrollToSelection:self];
	return self;
	}
	
	
- zoomAllOut:this_soundview
	{
	[self _zoomTo:1.0];
	[self scrollToSelection:self];
	return self;
	}
	


- zoomOutOneReduction:sender
	{
	id returnval=self;
	double scroll_value,reduction;
	int first_sample, number_of_samples;
	
	[window disableFlushWindow];
	[self getSelection:&first_sample size:&number_of_samples];
	reduction=[self reductionFactor];
	[self setReductionFactor:(int)reduction+1];
	scroll_value=[self _scrollValue];
	
	if (scroll_value>1.0||scroll_value<0.0)	
		{
		if (scroll_value>1.0) scroll_value=1.0;
		if (scroll_value<0.0) scroll_value=0.0;
		returnval=[self _setScrollValue:scroll_value];
		}
	[self setSelection:first_sample size: number_of_samples];
	[self scrollToSelection:self];
	[window reenableFlushWindow];
	[window flushWindowIfNeeded];
	return returnval;
	}


- zoomInOneReduction:sender
	{
	id returnval=self;
	double scroll_value;
	double reduction;
	int first_sample, number_of_samples;
	
	[window disableFlushWindow];
	[self getSelection:&first_sample size:&number_of_samples];
	reduction=[self reductionFactor];
	if (reduction>=1.0) [self setReductionFactor:(int)reduction-1];
	scroll_value=[self _scrollValue];
	if (scroll_value>1.0||scroll_value<0.0)	
		{
		if (scroll_value>1.0) scroll_value=1.0;
		if (scroll_value<0.0) scroll_value=0.0;
		returnval=[self _setScrollValue:scroll_value];
		}
	[self setSelection:first_sample size: number_of_samples];
	[self scrollToSelection:self];
	[window reenableFlushWindow];
	[window flushWindowIfNeeded];
	return returnval;
	}



- zoomToSelection:sender
	{
	int first_sample, number_of_samples, total_samples;
	double reduction_factor;
	NXRect visible_rect;
	
	// Step 0:  Get preliminary information
	
	if (self==NULL) return NULL;
	[window disableFlushWindow];	
	
	total_samples=[sound sampleCount];
	[self getSelection:&first_sample size:&number_of_samples];
	
	[self getVisibleRect:&visible_rect];

	// Step 1:  Zoom to the right reduction
	
	reduction_factor=MISCSOUNDVIEW_round(
			((double) number_of_samples) / ((double) visible_rect.size.width));
	if (reduction_factor<1.0) reduction_factor=1.0;
	[self setReductionFactor: reduction_factor];
	[self setSelection:first_sample size: number_of_samples];

		
	// Step 2:  Move to the right spot
	
	[self scrollToSelection:self];

	// Step 3:  Update Information
	[self display];
	[window reenableFlushWindow];
	[window flushWindowIfNeeded];
	return self;
	}
	
- read:(NXTypedStream *)stream
	{
	[super read:stream];
	NXReadTypes(stream,"ifiii",	&display_x_axis_marks,&minor_tick_spacing,
								&minor_tick_spacing_format,&major_tick_spacing,
								&display_labels);
	play_mark=-1; old_play_mark=-1;
	NXReadTypes(stream,"iiii",	&display_y_axis_grid,&display_zero_line,
								&y_display_format,&scroll_to_reflect_playing);
	only_change_play_mark=NO;
	[self _wipeClean];
	return self;
	}
	
- write:(NXTypedStream *)stream
	{
	[super write:stream];
	NXWriteTypes(stream,"ifiii",&display_x_axis_marks,&minor_tick_spacing,
								&minor_tick_spacing_format,&major_tick_spacing,
								&display_labels);
	NXWriteTypes(stream,"iiii",	&display_y_axis_grid,&display_zero_line,
								&y_display_format,&scroll_to_reflect_playing);
	return self;
	}

- awake
	{
	id returnval=[super awake];
	// do nothing for the time being...
	return returnval;
	}
	
- awakeFromNib
	{
	id returnval=[super awakeFromNib];
	[self adjustBounds:self];
	return returnval;
	}
	
- toggleXAxisDisplayed:sender
	{
	display_x_axis_marks=!display_x_axis_marks;
	return [self adjustBounds:self];
	}
	
- toggleYAxisDisplayed:sender	
	{
	display_y_axis_grid=!display_y_axis_grid;
	[self _wipeClean];
	[self display];
	return self;
	}

- toggleLabelsDisplayed:sender
	{
	display_labels=!display_labels;
	[self _wipeClean];
	[self display];
	return self;
	}

- toggleZeroLineDisplayed:sender
	{
	display_zero_line=!display_zero_line;
	[self _wipeClean];
	[self display];
	return self;
	}
	
- toggleScrollToReflectPlaying:sender
	{
	scroll_to_reflect_playing=!scroll_to_reflect_playing;
	return self;
	}

- (const char*) getInspectorClassName
	{
	return "MiscSoundViewInspector";
	}

@end
